# main.py
import json
import redis
import uvicorn
from fastapi import FastAPI, WebSocket, WebSocketDisconnect, HTTPException, status
from pydantic import BaseModel, Field
from typing import Dict, List, Optional
import random
import asyncio

# ---------- 配置 ----------
r = redis.Redis(host='localhost', port=6379, db=0, decode_responses=True)
app = FastAPI()
ROOM_PREFIX = "room:"
PLAYER_PREFIX = "player:"
VOTE_PREFIX = "vote:"

# ---------- 模型 ----------


class CreateRoomReq(BaseModel):
    config: Dict[str, int]  # {"狼":3,"预言家":1,"女巫":1,"猎人":1,"平民":3}


class JoinReq(BaseModel):
    seat: int = Field(..., ge=1, le=15)


class VoteCastReq(BaseModel):
    round: int
    target: int       # 0 表示弃票


# ---------- 工具 ----------
def room_key(room_id: str) -> str:
    return f"{ROOM_PREFIX}{room_id}"


def player_key(room_id: str, seat: int) -> str:
    return f"{PLAYER_PREFIX}{room_id}:{seat}"


def vote_key(room_id: str) -> str:
    return f"{VOTE_PREFIX}{room_id}"


def new_room_id() -> str:
    return str(random.randint(100000, 999999))


def publish(room_id: str, event: str, data: dict):
    msg = json.dumps({"event": event, "data": data})
    r.publish(f"channel:{room_id}", msg)

# ---------- HTTP 接口 ----------


@app.post("/room/create")
def create_room(req: CreateRoomReq):
    rid = new_room_id()
    r.hset(room_key(rid), mapping={
        "config": json.dumps(req.config),
        "locked": 0
    })
    # 初始化座位空
    for s in range(1, 16):
        r.hset(player_key(rid, s), mapping={"seat": s, "alive": 1, "role": "", "uid": ""})
    return {"room_id": rid}


@app.post("/room/{room_id}/join")
def join_room(room_id: str, req: JoinReq, uid: str = "test"):  # uid 应由微信登录替换
    rk = room_key(room_id)
    if not r.exists(rk):
        raise HTTPException(status_code=404, detail="房间不存在")
    if int(r.hget(rk, "locked")):
        raise HTTPException(status_code=409, detail="房间已锁座")
    pk = player_key(room_id, req.seat)
    if r.hget(pk, "uid"):
        raise HTTPException(status_code=409, detail="该位置已有人")
    r.hset(pk, mapping={"uid": uid, "alive": 1, "role": ""})
    publish(room_id, "join", {"seat": req.seat, "uid": uid})
    return {"success": True}


@app.post("/room/{room_id}/leave")
def leave_room(room_id: str, seat: int, uid: str = "test"):
    pk = player_key(room_id, seat)
    if r.hget(pk, "uid") != uid:
        raise HTTPException(status_code=403, detail="不是你的位置")
    r.hset(pk, mapping={"uid": "", "alive": 1, "role": ""})
    publish(room_id, "leave", {"seat": seat})
    return {"success": True}


@app.post("/room/{room_id}/newGame")
def new_game(room_id: str):
    """清空上一局数据，保留配置，座位不解锁"""
    rk = room_key(room_id)
    cfg = r.hget(rk, "config")
    r.delete(vote_key(room_id))
    # 重置玩家
    for s in range(1, 16):
        pk = player_key(room_id, s)
        if r.hget(pk, "uid"):
            r.hset(pk, mapping={"alive": 1, "role": ""})
    r.hset(rk, mapping={"config": cfg, "locked": 0})
    publish(room_id, "newGame", {})
    return {"success": True}


@app.post("/vote/{room_id}/start")
def vote_start(room_id: str, round: int):
    """上帝开启第 round 轮投票"""
    key = vote_key(room_id)
    r.hset(key, mapping={"round": round, "status": "running"})
    publish(room_id, "voteStart", {"round": round})
    return {"success": True}


@app.post("/vote/{room_id}/cast")
def vote_cast(room_id: str, req: VoteCastReq, seat: int = 1):
    """玩家所在 seat 投给 target（0=弃票）"""
    key = vote_key(room_id)
    if r.hget(key, "status") != "running":
        raise HTTPException(status_code=409, detail="投票未开启")
    alive = int(r.hget(player_key(room_id, seat), "alive"))
    if not alive:
        raise HTTPException(status_code=403, detail="你已死亡")
    r.hset(key, f"ballot:{seat}", req.target)
    publish(room_id, "voteCast", {"seat": seat, "target": req.target})
    return {"success": True}


@app.post("/vote/{room_id}/end")
def vote_end(room_id: str):
    key = vote_key(room_id)
    r.hset(key, "status", "ended")
    # 统计
    ballots = r.hgetall(key)
    counter = {}
    for k, v in ballots.items():
        if k.startswith("ballot:"):
            t = int(v)
            counter[t] = counter.get(t, 0) + 1
    sorted_res = sorted(counter.items(), key=lambda x: (-x[1], x[0]))
    publish(room_id, "voteEnd", {"result": sorted_res})
    return {"result": sorted_res}


@app.put("/player/{room_id}/{seat}")
def player_status(room_id: str, seat: int, status: int, god: str = "god"):
    """上帝把玩家状态置为 -1 死 / 0 放逐 / 1 活"""
    if god != "god":
        raise HTTPException(status_code=403, detail="需要上帝权限")
    pk = player_key(room_id, seat)
    r.hset(pk, "alive", status)
    publish(room_id, "statusChange", {"seat": seat, "alive": status})
    return {"success": True}

# ---------- WebSocket 广播 ----------


@app.websocket("/ws/{room_id}")
async def websocket_endpoint(websocket: WebSocket, room_id: str):
    await websocket.accept()
    pubsub = r.pubsub()
    await pubsub.subscribe(f"channel:{room_id}")
    try:
        while True:
            msg = pubsub.get_message(ignore_subscribe_messages=True, timeout=1)
            if msg:
                await websocket.send_text(msg['data'])
            await asyncio.sleep(0.1)
    except WebSocketDisconnect:
        await pubsub.close()

# ---------- 启动 ----------
if __name__ == "__main__":
    uvicorn.run("main:app", reload=True,port=8888)